#include "compressOutput_cpu.h"
#include <thrust/sort.h>
//#include "compress_gpu.cu"

static double total_sort_time = 0.0;
static double total_pos_comp_time = 0.0;
static double total_queryId_comp_time = 0.0;
static double total_misPos_comp_time = 0.0;
static double total_var_comp_time = 0.0;
static double total_misNum_comp_time = 0.0;

static uint32 total_pos_size = 0;
static uint32 total_queryId_size = 0;
static uint32 total_h_pos_size = 0;
static uint32 total_h_queryId_size = 0;
static uint32 total_misPos_size = 0;
static uint32 total_h_misPos_size = 0;

void RunTimeInfoInit()
{
	total_sort_time = 0.0;
	total_pos_size = 0;
	total_queryId_size = 0;
	total_h_pos_size = 0;
	total_h_queryId_size = 0;
	total_misPos_size = 0;
	total_h_misPos_size = 0;
	total_pos_comp_time = 0.0;
	total_queryId_comp_time = 0.0;
	total_misPos_comp_time = 0.0;
}


void printRunTimeInfo()
{
	printf("time for sort: %.2f\n", total_sort_time);
	printf("pos compress speed: %.2f\n", (double)(total_h_pos_size)/(double)total_pos_comp_time);
	printf("pos compress ratio: %.2f\n", (double)total_h_pos_size / (double)total_pos_size );
	printf("queryId compress speed: %.2f\n", (double)(total_h_queryId_size)/(double)total_queryId_comp_time);
	printf("queryId compress ratio: %.2f\n", (double)total_h_queryId_size / (double)total_queryId_size );
	printf("misPos compress speed: %.2f\n", (double)(total_h_misPos_size)/(double)total_misPos_comp_time);
	printf("misPos compress ratio: %.2f\n", (double)total_h_misPos_size / (double)total_misPos_size );
	printf("misNum compress time: %.2f\n",  total_misNum_comp_time);
	printf("var compress time: %.2f\n", total_var_comp_time);
}
void test_compress_blockSize(h_result* cpuSA, uint32 numSA1, FILE* resultFile, uint32 blockSize, uint32 Pos0)
{
	INIT_TIMER;	
	START_TIMER;
	RunTimeInfoInit();
	fseek(resultFile, Pos0, SEEK_SET);
	uint32 block_compressed_size;
	uint32 total_compressed_size = 0;
	printf("output data size: %u\n\n", numSA1 * sizeof(h_result));
	printf("blockSize:%u\n", blockSize);
	int i;	
	for(i=0; i<(numSA1/blockSize); i++) {
		compressResult(cpuSA + i*blockSize, blockSize, &block_compressed_size, resultFile);
		total_compressed_size += block_compressed_size; 
	}
	if(numSA1 % blockSize > 0) {
		compressResult(cpuSA + i*blockSize, numSA1%blockSize, &block_compressed_size, resultFile);
		total_compressed_size += block_compressed_size; 
	}
	uint64 curPos = ftell(resultFile);
	printf("compressed size: %u\n", total_compressed_size);
	printf("compressed ratio: %u\n", numSA1 * sizeof(h_result)/total_compressed_size);

	END_TIMER;
	PRINT_TIMER_SEC("total time:");
	printRunTimeInfo();
}

int compare (const void * a, const void * b)
{
  return ( ((struct h_result*)a)->pos > ((struct h_result*)b)->pos ) ? 1 : -1;
}

void getVariation(uint8* misPos, uint8* varBase, uint8* variations)
{
	if(variations[1] < variations[3]) {
		misPos[0] = variations[1];
		misPos[1] = variations[3] - variations[1];
		varBase[0] = variations[2];
		varBase[1] = variations[4];
	} else {
		misPos[0] = variations[3];
		misPos[1] = variations[1] - variations[3];
		varBase[0] = variations[4];
		varBase[1] = variations[2];
	}
}

void getParameter(uint32* data, uint32 dataSize, uint32* M)
{
	uint32 i;
	uint64 sum = 0;
	int logScale = 0;

	for(i=0; i<dataSize; i++) {
		sum += data[i];
	}

	uint32 temp = sum / dataSize;
	logScale = log((double)temp)/log(2.0);
	(*M) = pow(2.0, logScale);
	if((*M) == 0)
		(*M) = 2;
	else if((*M) > (1024*1024*512))
		(*M) = (1024*1024*512);
}


void getParameter(uint8* data, uint32 dataSize, uint32* M)
{
	uint32 i;
	uint32 sum = 0;
	int logScale = 0;

	for(i=0; i<dataSize; i++) {
		sum += data[i];
	}

	sum = sum / dataSize;
	logScale = log((double)sum)/log(2.0);
	(*M) = pow(2.0, logScale);
	if((*M) == 0)
		(*M) = 2;
}

void compressResult(h_result* resultBuffer, uint32 blockSize, uint32* compressed_size, FILE* fout)
{
	uint32 i;
	uint32 M_id = 1024*16, M_pos = 1024*1024, M_misPos = 8;

	INIT_TIMER;
#ifdef __SORT__	
	START_TIMER;	
	//sort result according to pos
	qsort(resultBuffer, blockSize, sizeof(resultBuffer[0]), compare);
//	thrust::sort(resultBuffer, resultBuffer+blockSize, resultCmp());	
	END_TIMER;
	total_sort_time += GET_TIMER_SEC;
#endif
	
	uint32* h_queryId = (uint32*)malloc(blockSize * sizeof(int));
	uint32* h_pos = (uint32*)malloc(blockSize * sizeof(int));
	uint8* c_h_pos = (uint8*)malloc(blockSize * sizeof(int));
	uint8* h_misNum = (uint8*)malloc(blockSize);
	uint8* c_h_misNum = (uint8*)malloc(ceil((double)blockSize/4.0));
	uint8* h_misPos = (uint8*)malloc(blockSize*2);
	uint8* h_varBase = (uint8*)malloc(blockSize*2);

	uint32 varPosOffset = 0, misPosOffset = 0,c_varPosOffset;
	uint64 c_h_posBitSize, c_h_idBitSize, c_h_misPosBitSize;
	uint32 c_h_posSize, c_h_idSize, c_h_misPosSize, c_h_misNumSize;

	for(i=0; i<blockSize; i++){
		h_queryId[i] = resultBuffer[i].queryId;
		h_pos[i] = resultBuffer[i].pos;
		h_misNum[i] = resultBuffer[i].variation[0];
		
		if(resultBuffer[i].variation[0] == 1){
			h_misPos[misPosOffset++] = resultBuffer[i].variation[1];
			h_varBase[varPosOffset++] = resultBuffer[i].variation[2];
		}
		else if(resultBuffer[i].variation[0] == 2){
			getVariation(h_misPos + misPosOffset, h_varBase + varPosOffset, resultBuffer[i].variation);
			misPosOffset += 2;
			varPosOffset += 2;
		}
	}

	printf("pos0: %u, id: %u, numMis: %u\n", h_pos[0], h_queryId[0], h_misNum[0]);

	FILE* fout0 = fopen("posFile","wb");
	fwrite(h_pos, sizeof(int), blockSize, fout0);
	fclose(fout0);

	FILE* fout1 = fopen("queryIdFile","wb");
	fwrite(h_queryId, sizeof(int), blockSize, fout1);
	fclose(fout1);
	
	FILE* fout2 = fopen("numMis","wb");
	fwrite(h_misNum, 1, blockSize, fout2);
	fclose(fout2);

	FILE* fout3 = fopen("misPos","wb");
	fwrite(h_misPos, 1, misPosOffset, fout3);
	fclose(fout3);

	FILE* fout4 = fopen("varBase","wb");
	fwrite(h_varBase, 1, varPosOffset, fout4);
	fclose(fout4);
	printf("file writing done!\nposFile %d\nqueryIdFile %d\nnumMis %d\nmisPos %d\n varBase %d\n",
			blockSize,blockSize,blockSize,misPosOffset,varPosOffset);

	//compress pos
	START_TIMER;
	cpu_delta_compress((int*)h_pos, blockSize, 1);
	getParameter(h_pos, blockSize, &M_pos);
	
//	double avgRLE = getAverageRunLen(h_pos, blockSize);
//	printf("average run length of pos after delta: %.2f\n", avgRLE);
	golombEncode(h_pos, c_h_pos, M_pos, blockSize, &c_h_posBitSize);


	c_h_posSize = ceil((double)c_h_posBitSize/8.0);
//	printf("uncompressed pos size: %u\n", blockSize * sizeof(int));	
//	printf("compressed pos size: %u\n", c_h_posSize);

	total_pos_size += c_h_posSize;
	total_h_pos_size += blockSize * sizeof(int);	
	END_TIMER;
	total_pos_comp_time += GET_TIMER_SEC;
	//compress queryId
	//M = ?
	START_TIMER;
	uint32 c_queryIdSize = 0;
	
	getParameter(h_queryId, blockSize, &M_id);
	uint8* c_h_queryId = (uint8*)malloc(blockSize * sizeof(int));
	golombEncode(h_queryId, c_h_queryId, M_id, blockSize, &c_h_idBitSize);
	c_h_idSize = ceil((double)c_h_idBitSize/8.0);
//	printf("uncompressed queryId size: %u\n", blockSize * sizeof(int));	
//	printf("compressed queryId size: %u\n", c_h_idSize);
//	printf("\n");
	total_queryId_size += c_h_idSize;
	total_h_queryId_size += blockSize * sizeof(int);

	END_TIMER;
	total_queryId_comp_time += GET_TIMER_SEC;

	START_TIMER;
	cpu_binaryEncoding_2bit(h_misNum, c_h_misNum, blockSize, &c_h_misNumSize);
	END_TIMER;
	total_misNum_comp_time += GET_TIMER_SEC;

	//compress misPos
	START_TIMER;
	uint32 c_misPosOffset = 0;
	getParameter(h_misPos, blockSize, &M_misPos);
	uint8* c_h_misPos = (uint8*)malloc(blockSize * 2);
	golombEncode(h_misPos, c_h_misPos, M_misPos, misPosOffset, &c_h_misPosBitSize);
	c_h_misPosSize = ceil((double)c_h_misPosBitSize/8.0);
//	printf("uncompressed misPos size: %u\n", misPosOffset);
//	printf("compressed misPos size: %u\n", c_h_misPosSize);
	total_misPos_size += c_h_misPosSize;
	total_h_misPos_size += misPosOffset;
	END_TIMER;
	total_misPos_comp_time += GET_TIMER_SEC;

	printf("M_id: %u, M_pos: %u, M_misPos: %u\n", M_id, M_pos, M_misPos);
	//binary compress dna variation
	START_TIMER;
	uint8* c_h_varBase = (uint8*)malloc(ceil((double)varPosOffset/4.0) * 4);
	cpu_binaryEncoding_2bit(h_varBase, c_h_varBase, varPosOffset, &c_varPosOffset);
	END_TIMER;
	total_var_comp_time += GET_TIMER_SEC;

//	START_TIMER
	fwrite(&blockSize, sizeof(int), 1, fout);
	fwrite(&M_id,sizeof(int),1,fout);
	fwrite(&c_h_idBitSize, sizeof(uint64), 1, fout);
	fwrite(&M_pos, sizeof(int), 1, fout);
	fwrite(&c_h_posBitSize, sizeof(uint64), 1, fout);
	fwrite(&M_misPos, sizeof(int), 1, fout);
	fwrite(&misPosOffset, sizeof(int), 1, fout);
	fwrite(&c_h_misPosBitSize, sizeof(uint64), 1, fout);
	fwrite(&varPosOffset, sizeof(int), 1, fout);
	fwrite(&c_varPosOffset, sizeof(int), 1, fout);
	
	fwrite(c_h_queryId, 1, c_h_idSize, fout);
	fwrite(c_h_pos, 1, c_h_posSize, fout);
	fwrite(c_h_misNum, 1, ceil((double)blockSize/4.0), fout);
	fwrite(c_h_misPos,1,c_h_misPosSize, fout);
	fwrite(c_h_varBase,1,c_varPosOffset, fout);

//	END_TIMER
//	PRINT_TIMER_SEC("compress disk access time: ");

	(*compressed_size) = c_h_idSize + c_h_posSize + ceil((double)blockSize/4.0) 
			+ c_h_misPosSize + c_varPosOffset;
	
	free(c_h_queryId);
	free(c_h_pos);
	free(c_h_misNum);
	free(c_h_misPos);
	free(c_h_varBase);

	free(h_queryId);
	free(h_misNum);
	free(h_pos);
	free(h_misPos);
	free(h_varBase);

}


uint32 decompressResult(h_result** resultBuffer_ptr, uint32* resultSize, FILE* fin)
{
	uint32 blockSize, M_id, M_pos, M_misPos, c_queryIdSize,
		varPosOffset = 0, c_varPosOffset = 0, c_h_posSize=0, misPosOffset = 0,c_misPosOffset;
	uint64 c_h_posBitSize, c_h_idBitSize, c_h_misPosBitSize;
	uint32 i;
	
	fread(&blockSize, sizeof(int), 1, fin);
	fread(&M_id,sizeof(int),1,fin);
	fread(&c_h_idBitSize, sizeof(uint64), 1, fin);
	fread(&M_pos, sizeof(int), 1, fin);
	fread(&c_h_posBitSize, sizeof(uint64), 1, fin);
	fread(&M_misPos, sizeof(int), 1, fin);
	fread(&misPosOffset, sizeof(int), 1, fin);
	fread(&c_h_misPosBitSize, sizeof(uint64), 1, fin);
	fread(&varPosOffset, sizeof(int), 1, fin);
	fread(&c_varPosOffset, sizeof(int), 1, fin);


	h_result* resultBuffer = (h_result*)malloc(blockSize * sizeof(h_result));
	uint32* h_queryId = (uint32*)malloc(blockSize * sizeof(int));
	uint32* h_pos = (uint32*)malloc(blockSize * sizeof(int));
	uint8* c_h_misNum = (uint8*)malloc(ceil((double)blockSize/4.0));
	uint8* h_misNum = (uint8*)malloc(ceil((double)blockSize/4.0) * 4);
	
	
	c_queryIdSize = ceil((double)c_h_idBitSize/8.0);
	uint8* c_h_queryId = (uint8*)malloc(c_queryIdSize);
	c_h_posSize = ceil((double)c_h_posBitSize/8.0);
	uint8* c_h_pos = (uint8*)malloc(c_h_posSize);
	c_misPosOffset = ceil((double)c_h_misPosBitSize/8.0);
	uint8* c_h_misPos = (uint8*)malloc(c_misPosOffset);
	uint8* h_misPos = (uint8*)malloc(misPosOffset);
	uint8* c_h_varBase = (uint8*)malloc(c_varPosOffset);
	uint8* h_varBase = (uint8*)malloc(c_varPosOffset*4);

	INIT_TIMER
	START_TIMER

	fread(c_h_queryId, 1, c_queryIdSize, fin);
	fread(c_h_pos, 1, c_h_posSize, fin);
	fread(c_h_misNum, 1, ceil((double)blockSize/4.0), fin);
	fread(c_h_misPos,1,c_misPosOffset, fin);
	fread(c_h_varBase,1, c_varPosOffset, fin);
	
	END_TIMER
	PRINT_TIMER_SEC("decompress read from disk time: ");

	//decompress queryId
	golombDecode(c_h_queryId, h_queryId, M_id, c_h_idBitSize);

	//decompress positions
	golombDecode(c_h_pos, h_pos, M_pos, c_h_posBitSize);
	cpu_delta_decompress((int*)h_pos, blockSize, 1);
	
	//decompress misNum
	cpu_binaryDecoding_2bit(c_h_misNum, h_misNum, ceil((double)blockSize/4.0));
	
	//decompress mismatch pos
	golombDecode(c_h_misPos, h_misPos, M_misPos, c_h_misPosBitSize);

	//decompress variations	
	cpu_binaryDecoding_2bit(c_h_varBase, h_varBase, c_varPosOffset);

	varPosOffset = 0;
	misPosOffset = 0;
	for(i=0; i<blockSize; i++) {
		resultBuffer[i].queryId = h_queryId[i];
		resultBuffer[i].pos = h_pos[i];
		resultBuffer[i].variation[0] = h_misNum[i];
		if(h_misNum[i] > 0) {
			resultBuffer[i].variation[1] = h_misPos[misPosOffset++];
			resultBuffer[i].variation[2] = h_varBase[varPosOffset++];
		}
		if(h_misNum[i] > 1){
			resultBuffer[i].variation[3] = h_misPos[misPosOffset] + h_misPos[misPosOffset-1];
			misPosOffset++;
			resultBuffer[i].variation[4] = h_varBase[varPosOffset++];		
		}
	}
	(*resultBuffer_ptr) = resultBuffer;
	(*resultSize) = blockSize;

	free(c_h_queryId);
	free(c_h_pos);
	free(c_h_misNum);
	free(c_h_misPos);
	free(c_h_varBase);

	free(h_queryId);
	free(h_misNum);
	free(h_pos);
	free(h_misPos);
	free(h_varBase);

	return blockSize;
}

void CPUPrintResult(h_result* outSA, uint32 numSA, FILE* outFile)
{
        uint32 i;
        for(i=0; i<numSA; i++) {
                fprintf(outFile, "%u %u %d ", outSA[i].queryId, outSA[i].pos, outSA[i].variation[0]);
                if(outSA[i].variation[0] > 0)
                        fprintf(outFile, "%d %d ", outSA[i].variation[1], outSA[i].variation[2]);
                if(outSA[i].variation[0] > 1)
                        fprintf(outFile, "%d %d ", outSA[i].variation[3], outSA[i].variation[4]);
                fprintf(outFile,"\n");
        }
}

uint8 getDNA(uint8 c) {
	if(c == 0)
		return 'A';
	else if(c==1)
		return 'C';
	else if(c==2)
		return 'G';
	else 
		return 'T';
}

void print_full_size_result_cpu(h_result* outSA, uint32 numSA, FILE* outFile, 
								char* titleBuffer,const int max_title_size, char* ref, 
								const int queryLen)
{
	PRINT_FUNC_NAME;
	INIT_TIMER;
	START_TIMER;

        uint32 i,j,k,tempPos;
        for(i=0; i<numSA; i++) {
				fprintf(outFile, "%s ", &(titleBuffer[outSA[i].queryId * max_title_size]));
				tempPos = outSA[i].pos;
				for(j=0; j< queryLen; j++) {
					if(ref[tempPos] == '\n')
						tempPos++;
					fprintf(outFile,"%c",ref[tempPos]);
					tempPos++;
				}
				fprintf(outFile," ");
                fprintf(outFile, "%d ",outSA[i].variation[0]);

                if(outSA[i].variation[0] > 0)
                        fprintf(outFile, "%d%c", outSA[i].variation[1], getDNA(outSA[i].variation[2]));
                if(outSA[i].variation[0] > 1)
                        fprintf(outFile, "%d%c", outSA[i].variation[3], getDNA(outSA[i].variation[4]));
                fprintf(outFile,"\n");
        }
	END_TIMER;
	PRINT_TIMER_SEC("write to disk time: ");
}

/*
int main()
{
	char* inFileName = "output";
	char* outFileName = "compressedOutput";
	char* outFileName2 = "decompressedOutput";
	const int dataSize = 63645427;
	const int blockSize = 10000000;
	uint32 total_compressed_size = 0, block_compressed_size = 0;
	FILE* fin = fopen(inFileName, "r");
	FILE* fout = fopen(outFileName, "wb");
	h_result* outputBuffer = (h_result*)malloc(dataSize * sizeof(h_result));
	for(int i=0; i<dataSize; i++) {
		fscanf(fin,"%u %u %d" , &(outputBuffer[i].queryId), &(outputBuffer[i].pos), &(outputBuffer[i].variation[0]));
		if(outputBuffer[i].variation[0] == 1)
			fscanf(fin, "%d %d",&(outputBuffer[i].variation[1]),&(outputBuffer[i].variation[2]));
		else if(outputBuffer[i].variation[0] == 2)
			fscanf(fin, "%d %d %d %d",&(outputBuffer[i].variation[1]),&(outputBuffer[i].variation[2]),
			&(outputBuffer[i].variation[3]),&(outputBuffer[i].variation[4]));
		fscanf(fin,"\n");
	}
	INIT_TIMER;
	START_TIMER;
	for(int i=0; i<dataSize/blockSize ; i++) {
		printf("block %d\n",i);
		compressResult(outputBuffer + i*blockSize, blockSize, &block_compressed_size, fout);
		printf("before compress: %u, after compress: %u\n\n", blockSize*sizeof(h_result), 
				block_compressed_size);
		total_compressed_size += block_compressed_size;
	}
	printf("block %d\n",dataSize/blockSize);
	compressResult(outputBuffer + dataSize/blockSize*blockSize, dataSize%blockSize, &block_compressed_size, fout);
	printf("before compress: %u, after compress: %u\n\n", (dataSize %blockSize)*sizeof(h_result), 
			block_compressed_size);
	total_compressed_size += block_compressed_size;
	printf("total compressed size: %u\n", total_compressed_size);
	printf("compression done\n\n\n");
	END_TIMER;
	PRINT_TIMER_SEC("compression time: ");

	fclose(fin);
	fclose(fout);
	free(outputBuffer);

	FILE* fin2 = fopen(outFileName, "rb");
	FILE* fout2 = fopen(outFileName2, "w");
	
	START_TIMER;
	uint32 resultSize = 0, totalSize = 0;
	while(totalSize < dataSize) {
		totalSize += decompressResult(&outputBuffer, &resultSize, fin2);
		CPUPrintResult(outputBuffer, resultSize, fout2);
		free(outputBuffer);
	}
	printf("decompression done\n\n\n");
	END_TIMER;
	PRINT_TIMER_SEC("decompression time: ");
	fclose(fin2);
	fclose(fout2);
	return 0;
}
*/
